#include "shell.h"

/**
 * Index of last command found in line
 */
int commandFound = 0;


/**
* Takes the value of one when an exception is raised during the parsing of the command line
*/
int exceptionRaised = 0;


/**
 * Initialize a new task
 */
static void initTask(Task* t);

/**
 * Debug functions used to print a task
 */
static void printTask(Task task);

/**
 * Handler used to wake up all pending objects
 */
static void waitingObjectHandler(int sig);


/**
 * Handler called when a real time signal SIGRTMIN+1 is catched. This handler frees the bloc memory identified by bloc num in si_sigval structure
 */
static void freeSigHandler(int sig, siginfo_t* info, void *useless);

/**
 * Sig handler used to managed real time signal SIGRTMIN+1. When such a signal is received, then
 * the sig handler function will be called and check whether the bloc identified by the data received in the pointer
 */
static void initSighandler(void);


/**
 * Search for needle in the command line keyword array and return 1 if needle was found as a keywork given on the command line
 * false otherwise
 * @param needle the string to search
 * @param command the command line in which to search the string
 * @return 1 if needle was found, 0 otherwise
 */
static int commandLineContains(char* needle, Command* command);


static int commandLineContains(char* needle, Command* command){
	if (needle == NULL || strlen(needle) == 0) return 0;
	int i;
	for (i = 0; i < MAX_CMD_INLINE; i++){
		if (strcmp(command -> keyword[i], needle) == 0) return 1;
	}
	return 0;
}


void createMkfifo(void){
	mode_t mode= S_IRUSR | S_IRGRP | S_IROTH | S_IWUSR | S_IWGRP | S_IWOTH;
	if (mkfifo(NAMED_FIFO, mode) == -1) {
		perror("mkfifo()");
	}
}

void closeMkfifo(void){
	if (unlink(NAMED_FIFO) == -1){
		perror("Could not close mkfifo");
		exit(EXIT_FAILURE);
	}
}

void endAllObjectProcess(void){
	extern InstanceQueue queue;
	Instance* ins;	
	while ((ins = popInstance(&queue)) != NULL){
		/* printInstance(*ins); */
		if (kill(ins->pid, SIGINT) == -1){
			perror("Terminating processes failed");
			exit(EXIT_FAILURE);
		}
	}
}


void shellInit(void){
	/* Create mkfifo */
	createMkfifo();
	/* Unbuffer the input stream */
	setvbuf(stdin, NULL, _IONBF, 0);
	/* Set memory up*/
	allocMemory();
	/* Init signal handler used to free return bloc */
	initSighandler();
	/* Init instance queue */
	extern InstanceQueue queue;	
	initQueue(&queue);
	printf(">");
	while (1){
	 	fgets(userData, MAX_CMDLINE_LEN, stdin);
	 	char* line = strchr(userData, '\n');
	 	if (line != NULL){
	 		dispatchTask();
	 		printf(">");	
	 	}
	}
}

void exitShell(void){
	closeMkfifo();
	endAllObjectProcess();
	freeMemory();
	exit(EXIT_SUCCESS);
}

void dispatchTask(void){
	/* No line entered, we do not need to dispatch the command */
	if (strlen(userData) == 1) return;
	Command command;
	parseCommandLine(userData, &command);
	/* Do not dispatch task if no valid command were found or if an exception was thrown */	
	if (commandFound == 0) return;
	commandFound = 0;
	extern int exceptionRaised;
	if (exceptionRaised == 1) {
		exceptionRaised = 0;
		return;
	}
	/* Start dispatch */
	if (commandLineContains("quit", &command) == 1){
		exitShell();
	}
	if (commandLineContains("free", &command) == 1){
		processFree(command);
	}
	if (commandLineContains("new", &command) == 1){
		processObjectCreation(command);
	}
	if (commandLineContains("waitfor", &command) == 1){
		processWaitfor(command);
	}
	if (commandLineContains("waitall", &command) == 1){
		processWaitall();
	}
	if (commandLineContains("work", &command) == 1){
		processWork(command);
	}
}



static void initTask(Task* t){
	t->pid = -1;
	t->blocReturnId = -1;
	t->blocParamId = -1;
	memset(t->task, '\0',sizeof(t->task));
}

void processObjectCreation(Command command){	
	Instance ins;
	initInstance(&ins);
	if (pipe(ins.pipe) == -1){
		perror("pipe() creation between shell and objects failed");
		exit(EXIT_FAILURE);
	}
	int pid = fork();
	extern InstanceQueue queue; 
	switch (pid){
		case 0:
			/* Son stdin becomes the pipe read fd */
			dup2(ins.pipe[0], STDIN_FILENO);
			if (close(ins.pipe[1]) == -1){
				perror("close() object pipe in failed");
				exit(EXIT_FAILURE);
			}
			if (execlp("../bin/launch", "launch", command.varName, MEM_FILE, NULL) == -1){
				perror("execlp() object creation failed");
				exit(EXIT_FAILURE);
			}
		case -1:
			perror("fork() object creation");
			exit(EXIT_FAILURE);	
		default:
			ins.pid = pid;
			strcpy(ins.callerObject, command.callerObject);	
			if (close(ins.pipe[0]) == -1){
				perror("close() object pipe in failed");
				exit(EXIT_FAILURE);
			}		
			break;
	}
	/* We store the new process */
	pushInstance(&queue, ins);
}

void processWork(Command command){
	Task t;
	initTask(&t);
	extern InstanceQueue queue;
	/* printf("Retrieve instance : %s\n", command.callerObject); */
	Instance tmpIns;
	strcpy(tmpIns.callerObject, command.callerObject);
	Instance* ins = getInstanceFromName(&queue, &tmpIns, &compareInstanceByCallerObjectName);
	if (ins == NULL){
		printf("Error retrieving instance (processWork)\n");
		exitShell();
	}
	strcpy(ins -> varName, command.varName);
	t.pid = ins -> pid;
	strcpy(t.task, "work");
	/* Retrieve a free mem bloc for param if any param */
	int blocParamId = -1;
	if (command.fncArgs[0][0] != '\0'){
		blocParamId = getNewBloc();
		void* buffer[BLOC_LENGHT];
		memset(buffer, 0, BLOC_LENGHT);
		int i = 0;
		int bufoffset = 0;
		while (command.fncArgs[i][0] != '\0'){
			if (isNumeric(command.fncArgs[i])){
				int num = atoi(command.fncArgs[i]);
				bcopy(&num, buffer + bufoffset, sizeof(int));
				bufoffset += 4;
			}
			else if (strchr(command.fncArgs[i], '"') != NULL){
				/* Its a string */
				strreplace(command.fncArgs[i], '"', ' ');
				trim(command.fncArgs[i], command.fncArgs[i]);
				int len = strlen(command.fncArgs[i]) + 1;
				bcopy(command.fncArgs[i], buffer + bufoffset, len);
				bufoffset += len;
			}
			else {
				InstanceMemory tmpIns;
				strcpy(tmpIns.varName, command.fncArgs[i]);
				InstanceMemory* ins = getInstanceMemory(&tmpIns, compareInstanceMemByVarName);
				if (ins == NULL){
					printf("Error retrieve memory bloc associated to %s", command.fncArgs[i]);
					return;
				}
				char* bloc = getBloc(ins -> blocReturnId);
				int j = 0, lastIndexEndOfString = 0, endOfStringCounter = 0;
				for (j = 0; j < BLOC_LENGHT && endOfStringCounter < 2; j++){
					char* ptr = bloc + j;
					if (ptr[0] == '\0' && ((j - 1) == lastIndexEndOfString || endOfStringCounter == 0)) {
						lastIndexEndOfString = j;
						endOfStringCounter++;
					}
				}
				bcopy(bloc, buffer + bufoffset, j);
				bufoffset += j;
			}
			i++;
		}
		setBloc(blocParamId, buffer);
	}
	/* Retrieve a return block for param if return result is catched in variable */
	int blocReturnId = getNewBloc();
	t.blocParamId = blocParamId;
	t.blocReturnId = blocReturnId;
	/* Store the bloc result id and associates its to the varname if function call return value is assigned */
	if (command.varName[0] != '\0'){
		extern InstanceMemoryQueue instanceMemQueue;
		InstanceMemory memory;
		initInstanceMemory(&memory);
		strcpy(memory.varName, command.varName);
		memory.blocReturnId = blocReturnId;
		instanceMemQueue.queue[instanceMemQueue.index++] = memory;
		/* printInstanceMemory(memory); */
	}
	/* Sends the work task */
	sendTask(&t, ins -> pipe[1]);
}


void sendTask(Task* t, int destinationFd){
	/* printf("Sending task\n"); */
	/* printTask(*t); */
	int bytesWritten;
	if ((bytesWritten = write(destinationFd, t, sizeof(Task))) == -1){
		perror("Error sending task");	
	}
}
static void printTask(Task task){
	printf("Shell id : '%d'\nTask : '%s'\nBloc p id : '%d'\nBloc r id : '%d'\n",
	task.pid,
	task.task,
	task.blocParamId,
	task.blocReturnId
	);
}

void processFree(Command command){
	InstanceMemory tmpIns;
	strcpy(tmpIns.varName, command.varName);
	InstanceMemory* instanceMemory = getInstanceMemory(&tmpIns, compareInstanceMemByVarName);
	if (instanceMemory == NULL){
		printf("Error retrieving bloc to free it (processFree)");
		return;	
	}
	if (commandLineContains("waitfor", &command)){
		/* Send free task to object that we are waiting for as free must be used after */
		Task t;
		initTask(&t);
		strcpy(t.task, "free");
		extern InstanceQueue queue;
		Instance tmpIns;
		strcpy(tmpIns.callerObject, command.targetObject);
		Instance* ins = getInstanceFromName(&queue, &tmpIns, compareInstanceByCallerObjectName);
		if (ins == NULL){
			printf("Error retrieving instance (processFree)");
			printInstance(tmpIns);
			return;
		}
		sendTask(&t, ins->pipe[1]);	
	}
	else {
		instanceMemory -> freed = 1;
		freeBloc(instanceMemory ->blocReturnId);
		printf("Free bloc %d corresponding to var : %s\n",instanceMemory->blocReturnId, instanceMemory->varName);
	}
}

void processWaitfor(Command command){
	if (commandLineContains("free", &command) == 1) return;
	Task t;
	initTask(&t);
	Instance tmp;
	strcpy(tmp.callerObject, command.callerObject); 
	extern InstanceQueue queue;
	Instance* instanceToStop = getInstanceFromName(&queue, &tmp, &compareInstanceByCallerObjectName);
	if (instanceToStop == NULL){
		printf("Undefined object (processWaitFor)\n");
		return;	
	}
	t.pid = instanceToStop -> pid;
	/* Tells the caller object to stop */
	kill(instanceToStop -> pid, SIGSTOP);
	/* Send waitfor command to target object */
	strcpy(tmp.callerObject, command.targetObject);
	Instance* targetInstance = getInstanceFromName(&queue, &tmp, &compareInstanceByCallerObjectName);
	if (targetInstance == NULL){
		printf("Cannot wait for undefined object (processWaitFor)\n");
		return;	
	}
	strcpy(t.task,"waitfor");
	sendTask(&t, targetInstance->pipe[1]);
}


void processWaitall(void){
	extern InstanceQueue queue;
	int i;
	Task t;
	initTask(&t);
	strcpy(t.task, "waitall");
	for (i = 0; i < queue.index;i++){
		sendTask(&t, queue.instances[i].pipe[1]);
	}
}

static void freeSigHandler(int sig, siginfo_t* info, void *useless){
	if (info -> si_code != SI_QUEUE) return;
	/* Ok, Signal was sent using the signalqueue() method */
	int blocId = info -> si_value.sival_int;
	/*printf("Signal received with bloc id %d\n", blocId);*/
	InstanceMemory tmpIns;
	tmpIns.blocReturnId = blocId;
	InstanceMemory* instanceMemory = getInstanceMemory(&tmpIns,compareInstanceMemById);
	if (instanceMemory == NULL){
		/*printf("Free bloc : %d\n", blocId);*/
		freeBloc(blocId);
	}
}

static void waitingObjectHandler(int sig){
	static int counter = 0;
	counter++;
	extern InstanceQueue queue;
	int i;
	if (counter == 	queue.index){
		for (i = 0; i < queue.index;i++){
			if (kill(queue.instances[i].pid, SIGUSR1) == -1){
				perror("Cannot wake up pending objects");
				continue;
			}
		}
		counter = 0;
	}
}

static void initSighandler(void){
	struct sigaction freesigaction;
    sigemptyset(&freesigaction.sa_mask);
	freesigaction.sa_flags = SA_SIGINFO; /* Calls the function pointed by field sa_sigaction instead of basic signal handler function */
	freesigaction.sa_sigaction = freeSigHandler;
	sigaction(SIGRTMIN+1, &freesigaction, NULL);
	sigaction(SIGRTMIN+2, &freesigaction, NULL);
	struct sigaction waitallaction;
	sigemptyset(&waitallaction.sa_mask);
	waitallaction.sa_flags = 0;
	waitallaction.sa_handler = waitingObjectHandler;
	sigaction(SIGUSR1, &waitallaction, NULL);
}

